﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.UI.WebControls;
using System.Web;
using System.Collections;
using System.Web.DynamicData;
using System.Reflection;
using System.Web.UI;
using System.Data.Linq;

namespace Catalyst.Web.DynamicData
{
    /// <summary>
    /// The DynamicLinqDataSourceView extends the LinqDataSourceView to add the ability to create complex
    /// where predicates.  
    /// </summary>
    public class DynamicLinqDataSourceView : LinqDataSourceView
    {
        private DynamicParameterCollection _wherePredicateParameters;
        private LinqDataSource _owner;
        private HttpContext _context;

        /// <summary>
        /// Gets a collection of parameters that are used to create the Where predicate.
        /// </summary>
        /// <returns>
        /// A collection that contains the parameters that are used to generate the where predicate.
        /// </returns>
        public DynamicParameterCollection WherePredicateParameters
        {
            get
            {
                if (this._wherePredicateParameters == null)
                {
                    this._wherePredicateParameters = new DynamicParameterCollection();
                    this._wherePredicateParameters.ParametersChanged += new EventHandler(WherePredicateParameters_ParametersChanged);
                    if (this.IsTrackingViewState)
                    {
                        ((IStateManager)this._wherePredicateParameters).TrackViewState();
                    }
                }
                return this._wherePredicateParameters;
            }
        }

        /// <summary>
        /// Hack required to set wherePredicateParameters collection to dirty.
        /// </summary>
        private void WherePredicateParameters_ParametersChanged(object sender, EventArgs e)
        {
            Parameter parameter = new Parameter();
            this.WhereParameters.Add(parameter);
            this.WhereParameters.Remove(parameter);
        }

        public DynamicLinqDataSourceView(LinqDataSource owner, string name, HttpContext context)
            : base(owner, name, context)
        {
            _owner = owner;
            _context = context;
        }

        /// <summary>
        /// Override LoadViewState to include WherePredicateParameters in ViewState deserialization
        /// </summary>
        protected override void LoadViewState(object savedState)
        {
            if (savedState != null)
            {
                object[] objArray = (object[])savedState;
                if (objArray[0] != null)
                    base.LoadViewState(objArray[0]);
                if (objArray[1] != null)
                    ((IStateManager)this.WherePredicateParameters).LoadViewState(objArray[1]);
            }
        }

        /// <summary>
        /// Override SaveViewState to include WherePredicateParameters in ViewState serialization
        /// </summary>
        protected override object SaveViewState()
        {
            object[] objArray = new object[2];
            objArray[0] = base.SaveViewState();
            if ((WherePredicateParameters != null) && (WherePredicateParameters.Count > 0))
                objArray[1] = ((IStateManager)WherePredicateParameters).SaveViewState();

            return objArray;
        }

        /// <summary>
        /// Override TrackViewState to include Tracking on WherePredicateParameters
        /// </summary>
        protected override void TrackViewState()
        {
            base.TrackViewState();
            if ((WherePredicateParameters != null) && (WherePredicateParameters.Count > 0))
                ((IStateManager)WherePredicateParameters).TrackViewState();
        }

        /// <summary>
        /// Overrides default select logic to add WherePredicateParameters to the LINQ expression.
        /// </summary>
        /// <param name="e">The LinqDataSourceSelectEventArgs provided by the LinqDataSource</param>
        protected override void OnSelecting(LinqDataSourceSelectEventArgs e)
        {
            base.OnSelecting(e);

            foreach (Parameter p in this.WherePredicateParameters)
            {
                var compositeParameter = p as CompositeExpressionParameterBase;
                if (compositeParameter != null)
                    compositeParameter.GetValues(_context, _owner);
            }

            LinqDataSourceContextData data = CreateContextAndTable(DataSourceOperation.Select);
            IQueryable source = AsQueryable(data.Table);

            foreach (var parameter in WherePredicateParameters)
            {
                var queryableParameter = parameter as IDynamicExpressionParameter;
                if (queryableParameter != null)
                    source = queryableParameter.AppendQuery(source);
            }

            e.Result = source;
        }

        private class LinqDataSourceContextData
        {
            public object Context { get; set; }
            public object Table { get; set; }

            public LinqDataSourceContextData() { }

            public LinqDataSourceContextData(object context)
            {
                Context = context;
            }
        }

        #region "Private LinqDataSourceView members required to construct the table"

        private static IQueryable AsQueryable(object o)
        {
            IQueryable queryable = o as IQueryable;
            if (queryable != null)
                return queryable;

            string str = o as string;
            if (str != null)
                return new string[] { str }.AsQueryable<string>();
            
            IEnumerable source = o as IEnumerable;
            if (source != null)
            {
                if (FindGenericEnumerableType(o.GetType()) != null)
                    return source.AsQueryable();
                
                List<object> list = new List<object>();
                foreach (object obj2 in source)
                    list.Add(obj2);
                
                return list.AsQueryable<object>();
            }
            IList list2 = (IList)Activator.CreateInstance(typeof(List<>).MakeGenericType(new Type[] { o.GetType() }));
            list2.Add(o);
            return list2.AsQueryable();
        }

        private static Type FindGenericEnumerableType(Type type)
        {
            while (((type != null) && (type != typeof(object))) && (type != typeof(string)))
            {
                if (type.IsGenericType && (type.GetGenericTypeDefinition() == typeof(IEnumerable<>)))
                    return type;
                
                foreach (Type type2 in type.GetInterfaces())
                {
                    Type type3 = FindGenericEnumerableType(type2);
                    if (type3 != null)
                        return type3;
                }
                type = type.BaseType;
            }
            return null;
        }

        private LinqDataSourceContextData CreateContextAndTable(DataSourceOperation operation)
        {
            LinqDataSourceContextData data = null;
            bool flag = false;
            try
            {
                LinqDataSourceContextEventArgs e = new LinqDataSourceContextEventArgs(operation);
                this.OnContextCreating(e);
                data = new LinqDataSourceContextData(e.ObjectInstance);
                Type contextType = null;
                MemberInfo member = null;
                if (data.Context == null)
                {
                    contextType = this.ContextType;
                    member = this.GetTableMemberInfo(contextType);
                    if (member != null)
                    {
                        if (MemberIsStatic(member))
                        {
                            if (operation != DataSourceOperation.Select)
                                throw new InvalidOperationException();
                        }
                        else
                        {
                            data.Context = Activator.CreateInstance(contextType);
                        }
                    }
                }
                else
                {
                    member = this.GetTableMemberInfo(data.Context.GetType());
                }
                
                if (member != null)
                {
                    FieldInfo info2 = member as FieldInfo;
                    if (info2 != null)
                        data.Table = info2.GetValue(data.Context);
                    
                    PropertyInfo info3 = member as PropertyInfo;
                    if (info3 != null)
                        data.Table = info3.GetValue(data.Context, null);
                }
                if (data.Table == null)
                    throw new InvalidOperationException();
            }
            catch (Exception exception)
            {
                flag = true;
                LinqDataSourceStatusEventArgs args2 = new LinqDataSourceStatusEventArgs(exception);
                this.OnContextCreated(args2);
                this.OnException(new DynamicValidatorEventArgs(exception, DynamicDataSourceOperation.ContextCreate));
                if (!args2.ExceptionHandled)
                    throw;
            }
            finally
            {
                if (!flag)
                {
                    object result = (data == null) ? null : data.Context;
                    LinqDataSourceStatusEventArgs args3 = new LinqDataSourceStatusEventArgs(result);
                    this.OnContextCreated(args3);
                }
            }
            return data;
        }

        private static bool MemberIsStatic(MemberInfo member)
        {
            FieldInfo info = member as FieldInfo;
            if (info != null)
                return info.IsStatic;
            
            PropertyInfo info2 = member as PropertyInfo;
            if (info2 == null)
                return false;
            
            MethodInfo getMethod = info2.GetGetMethod();
            return ((getMethod != null) && getMethod.IsStatic);
        }

        #endregion
    }
}
